package com.cnap.multiclouldmgmt.service.impl;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.cnap.multiclouldmgmt.model.RepByPage;
import com.cnap.multiclouldmgmt.model.alg.*;
import com.cnap.multiclouldmgmt.model.enums.AlarmLevelEnum;
import com.cnap.multiclouldmgmt.model.enums.ErrorInjectOrderEnum;
import com.cnap.multiclouldmgmt.model.enums.K8sStatusEnum;
import com.cnap.multiclouldmgmt.model.exception.JsonException;
import com.cnap.multiclouldmgmt.model.k8sModels.K8sClusterBasicModel;
import com.cnap.multiclouldmgmt.model.k8sModels.PodModel;
import com.cnap.multiclouldmgmt.model.knowledge.*;
import com.cnap.multiclouldmgmt.repository.cnap.AlarmMapper;
import com.cnap.multiclouldmgmt.repository.cnap.RootCauseMapper;
import com.cnap.multiclouldmgmt.repository.master.KnowledgeMapper;
import com.cnap.multiclouldmgmt.service.AlgorithmService;
import com.cnap.multiclouldmgmt.service.HealthService;
import com.cnap.multiclouldmgmt.service.K8sClusterService;
import com.cnap.multiclouldmgmt.service.MultiCloudMgmtService;
import com.cnap.multiclouldmgmt.util.JsonUtil;
import com.cnap.multiclouldmgmt.util.PageUtils;
import com.cnap.multiclouldmgmt.util.TokenUtils;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.page.PageMethod;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.logging.Handler;
import java.util.stream.Collectors;

/**
 * <类文件描述>
 *
 * @author xc
 **/
@Service
public class HealthServiceImpl implements HealthService {

    private static final Logger LOGGER = LoggerFactory.getLogger(HealthServiceImpl.class);

    private final AlarmMapper alarmMapper;
    private final AlgorithmService algorithmService;

    private final KnowledgeMapper knowledgeMapper;
    private final RootCauseMapper rootCauseMapper;

    private final MultiCloudMgmtService multiCloudMgmtService;
    private final K8sClusterService k8sClusterService;

    @Autowired
    public HealthServiceImpl(AlarmMapper alarmMapper, AlgorithmService algorithmService, KnowledgeMapper knowledgeMapper,
                             RootCauseMapper rootCauseMapper,
                             MultiCloudMgmtService multiCloudMgmtService,
                             K8sClusterService k8sClusterService) {
        this.alarmMapper = alarmMapper;
        this.algorithmService = algorithmService;
        this.knowledgeMapper = knowledgeMapper;
        this.rootCauseMapper = rootCauseMapper;
        this.multiCloudMgmtService = multiCloudMgmtService;
        this.k8sClusterService = k8sClusterService;
    }

    @Override
    public String getHealthValue() {
        String token = TokenUtils.getTOKEN();
        if (Objects.isNull(token)) {
            LOGGER.error("token is null");
            throw new JsonException(401, "token已过期");
        }
        String userName = TokenUtils.getUserName();

        RequestForHealth request = new RequestForHealth();
        AlarmLevelInfo alarmLevelInfo = new AlarmLevelInfo();

        int infoNum = alarmMapper.countByAlarmLevelWithUser(AlarmLevelEnum.PROMPT.getMessage(), userName);
        int minorNum = alarmMapper.countByAlarmLevelWithUser(AlarmLevelEnum.MINOR.getMessage(), userName);
        int majorNum = alarmMapper.countByAlarmLevelWithUser(AlarmLevelEnum.MAJOR.getMessage(), userName);
        int criticalNum = alarmMapper.countByAlarmLevelWithUser(AlarmLevelEnum.EMERGENCY.getMessage(), userName);

        alarmLevelInfo.setInfo(infoNum);
        alarmLevelInfo.setMinor(minorNum);
        alarmLevelInfo.setMajor(majorNum);
        alarmLevelInfo.setCritical(criticalNum);
        request.setAlarmLevelInfo(alarmLevelInfo);

        ResponseForHealth result = algorithmService.calHealthValue(request);
        if (Objects.isNull(result)) {
            LOGGER.error("can't get health value from algorithm system");
            throw new JsonException(500, "无法计算健康度");
        }
        return result.getHealthValue();
    }

    @Override
    public List<KnowledgePO> getAllKnowledge() {
        return knowledgeMapper.selectAll();
    }

    @Override
    public void calRootCause(CalCauseParam calCauseParam) {
        // 转换请求参数
        RequestForCause request = calCauseParam.toRequest();
        // 发送请求
        ResponseForCause response = algorithmService.calRootCause(request);
        if (Objects.isNull(response)) {
            LOGGER.error("[case]: alg get null response, param={}", JsonUtil.objectToJsonStr(calCauseParam));
            return;
        }
        // 有可能没有计算处根因，此时就不需要插入
        if (response.getCode() == 400) {
            LOGGER.warn("[case]: alg fail to analysis alarm root case, param={}", JsonUtil.objectToJsonStr(calCauseParam));
            return;
        }

        // 组装根因
        RootCausePO rootCausePO = new RootCausePO();
        rootCausePO.setId(IdUtil.simpleUUID());
        rootCausePO.setAlarmId(calCauseParam.getAlarmId());
        rootCausePO.setRuleId(calCauseParam.getRuleId());
        rootCausePO.setStatus(CauseStatusEnum.WAIT_FIX.getStatus());
        rootCausePO.setKnowledgeId(response.getRootCaseId());
        rootCausePO.setCreateTime(LocalDateTime.now(ZoneId.of("Asia/Shanghai")));

        rootCauseMapper.insertRootCause(rootCausePO);


    }

    @Override
    public RepByPage<RootCaseVO> findByPage(QueryCaseParam queryCaseParam) {
        PageMethod.startPage(queryCaseParam.getPageNum(), queryCaseParam.getPageSize());
        List<RootCaseDTO> dtos = rootCauseMapper.selectByList();
        List<RootCaseVO> vos = dtos.stream().map(RootCaseDTO::toVO).collect(Collectors.toList());

        PageInfo<RootCaseDTO> pos = new PageInfo<>(dtos);
        PageInfo<RootCaseVO> voPageInfo = new PageInfo<>(vos);
        PageUtils.setRealPageInfo(voPageInfo, pos);
        return PageUtils.getPageResult(voPageInfo);
    }

    @Override
    public boolean deleteRootCauseById(String causeId) {
        return rootCauseMapper.deleteRootCauseById(causeId) == 1;
    }

    @Override
    public RepByPage<ErrorInjectSceneModel> getErrorInjectScenes(String keyword, int pageSize, int pageNo) {

        RepByPage<ErrorInjectSceneModel> result = new RepByPage<>();
        result.setPageNo(pageNo);
        result.setPageSize(pageSize);
        result.setTotalNum(0);
        result.setResult(new ArrayList<>());

        int totalNum = knowledgeMapper.getErrorInjectScenesNum(keyword);
        if (totalNum > 0){
            result.setTotalNum(totalNum);
            result.setResult(knowledgeMapper.getErrorInjectScenes(keyword,pageSize,(pageNo - 1)*pageSize));
        }

        return result;
    }

    @Override
    public void runErrorInject(int id) throws Exception {

        ErrorInjectModel errorInjectModel = knowledgeMapper.getErrorInjectRecordById(id);
        if (errorInjectModel == null){
            throw new Exception("该故障注入命令已删除");
        }
        if (StringUtils.equalsIgnoreCase(errorInjectModel.getInjectStatus(),K8sStatusEnum.Running.getDescribe())){

            throw new Exception("该故障注入命令已在运行中");
        }


        String runingId = null;
        if (ErrorInjectOrderEnum.checkErrorInjectRunOrder(errorInjectModel.getErrorKey())) {
            runingId = k8sClusterService.runErrorInject(errorInjectModel);
            if (StringUtils.isEmpty(runingId)) {
                throw new Exception("该故障注入命令执行失败");
            }

            knowledgeMapper.changeErrorInjectRecordRunningId(id, runingId);
            knowledgeMapper.changeErrorInjectRecordStatus(id, K8sStatusEnum.Running.getDescribe());
        }else if (StringUtils.equalsIgnoreCase(errorInjectModel.getErrorKey(),ErrorInjectOrderEnum.GpuError.getErrorKey())){

            //获取集群ip
            K8sClusterBasicModel k8sClusterBasicModel =
                    multiCloudMgmtService.getK8sClusterInfoByName(errorInjectModel.getK8sClusterName());
            if (StringUtils.equalsIgnoreCase(k8sClusterBasicModel.getK8sStatus(),K8sStatusEnum.Running.getDescribe())){
                throw new Exception("集群" + errorInjectModel.getK8sClusterName() + "状态异常");
            }
            String url = "http://" + k8sClusterBasicModel.getK8sClusterIp() + ":8088/";

            //组装参数，向集群的8088端口发送
            Map<String,Integer> paramMap = new HashMap<>();
            paramMap.put("time",Integer.parseInt(errorInjectModel.getErrorOrder()));

            String param = JSON.toJSONString(paramMap);
            try {

                String result = doHttpPostReq(url, new StringEntity(param));
            }catch (Exception e){
                LOGGER.error("do gpu error inject failed",e);
            }

        }else if (StringUtils.equalsIgnoreCase(errorInjectModel.getErrorKey(),ErrorInjectOrderEnum.MicroSvcPress.getErrorKey())){

            k8sClusterService.microSvcPress(errorInjectModel);

        }

    }

    @Override
    public void stopErrorInject(int id) throws Exception {
        ErrorInjectModel errorInjectModel = knowledgeMapper.getErrorInjectRecordById(id);
        if (errorInjectModel == null){
            throw new Exception("该故障注入命令已删除");
        }
        if (StringUtils.equalsIgnoreCase(errorInjectModel.getInjectStatus(),K8sStatusEnum.Stop.getDescribe())){

           return;
        }

        k8sClusterService.stopErrorInject(errorInjectModel);

        knowledgeMapper.changeErrorInjectRecordStatus(id,K8sStatusEnum.Stop.getDescribe());

    }

    @Override
    public void delErrorInject(int id) throws Exception {
        ErrorInjectModel errorInjectModel = knowledgeMapper.getErrorInjectRecordById(id);
        if (errorInjectModel == null){
            return;
        }
        if (StringUtils.equalsIgnoreCase(errorInjectModel.getInjectStatus(),K8sStatusEnum.Running.getDescribe())){

            throw new Exception("该故障注入命令正在运行，请先停止");
        }

        knowledgeMapper.delErrorInjectRecordById(id);
    }

    @Override
    public RepByPage<ErrorInjectModel> getErrorInjects(String keyword, int pageSize, int pageNo) {

        RepByPage<ErrorInjectModel> result = new RepByPage<>();
        result.setPageNo(pageNo);
        result.setPageSize(pageSize);

        result.setTotalNum(knowledgeMapper.getErrorInjectRecordNum(keyword));
        List<ErrorInjectModel> errorInjectModelList = new ArrayList<>();
        result.setResult(errorInjectModelList);

        if (result.getTotalNum() > 0) {
            List<ErrorInjectModel> errorInjectModels =
                    knowledgeMapper.getErrorInjectRecordByPage(keyword, (pageNo - 1) * pageSize, pageSize);
            if (!errorInjectModels.isEmpty()){

                for (ErrorInjectModel errorInjectModel:errorInjectModels){

                    errorInjectModel.setErrorResType(
                            ErrorInjectOrderEnum.getErrorResTypeByKey(errorInjectModel.getErrorKey()));
                    errorInjectModel.setErrorDesrcibe(
                            ErrorInjectOrderEnum.getErrorDescribeByKey(errorInjectModel.getErrorKey()));
                    errorInjectModelList.add(errorInjectModel);
                }
            }
        }

        return result;
    }

    @Override
    public void addErrorInject(ErrorConfigModel errorConfigModel,String userName) throws Exception {

        ErrorInjectModel oldErrorInjectModel =
                knowledgeMapper.getErrorInjectRecordByNameUser(errorConfigModel.getErrorName(),userName);
        if (oldErrorInjectModel != null){
            throw new Exception("模拟故障注入名称已经存在");
        }

        ErrorInjectModel errorInjectModel = new ErrorInjectModel();
        errorInjectModel.setUpdateTime(new Date());
        errorInjectModel.setUsername(userName);
        errorInjectModel.setErrorName(errorConfigModel.getErrorName());
        errorInjectModel.setErrorKey(errorConfigModel.getErrorKey());
        errorInjectModel.setInjectStatus(K8sStatusEnum.Stop.getDescribe());
        errorInjectModel.setK8sClusterName(errorConfigModel.getK8sClusterName());
        errorInjectModel.setNodeName(errorConfigModel.getNodeName());
        errorInjectModel.setPodName(errorConfigModel.getPodName());
        errorInjectModel.setMicroSvcId(errorConfigModel.getMicroSvcId());
        //根据参数生成order
        String order = null;
        if (StringUtils.equalsIgnoreCase(errorInjectModel.getErrorKey(),"node_cpu_num")){
            //参数是cpu数量，1,2,3
            order = "blade create cpu fullload --cpu-count " + errorConfigModel.getErrorParams().get(0);
        }else if (StringUtils.equalsIgnoreCase(errorInjectModel.getErrorKey(),"node_cpu_error")){

            //参数0，node名称，第二参数百分比
            order = "blade create k8s node-cpu fullload --names " + errorConfigModel.getNodeName()
                        + " --cpu-percent " + errorConfigModel.getErrorParams().get(0)
                        + " --kubeconfig ~/.kube/config";
        }else if(StringUtils.equalsIgnoreCase(errorInjectModel.getErrorKey(),"net_delay_error")){
            //参数0，网卡名称，第二参数延迟时间
            order = "blade create network delay --interface " + errorConfigModel.getErrorParams().get(0)
                    + " --time " + errorConfigModel.getErrorParams().get(1);
        }else if (StringUtils.equalsIgnoreCase(errorInjectModel.getErrorKey(),"node_pods_cpu_error")){

            //参数0 cpu1百分比，根据参数拿到contierid，
            K8sClusterBasicModel k8sClusterBasicModel =
                    multiCloudMgmtService.getK8sClusterInfoByName(errorConfigModel.getK8sClusterName());
            List<PodModel> podModels = k8sClusterService.getAllPodsInfo(k8sClusterBasicModel,null);
            PodModel podModel = new PodModel();
            for (PodModel tmpPodModel:podModels){
                if (StringUtils.equalsIgnoreCase(tmpPodModel.getPodName(),
                        errorConfigModel.getPodName())){
                    podModel = tmpPodModel;
                    break;
                }
            }
            String containId = podModel.getContainerModels().get(0).getContainerId();
            order = "blade create k8s container-cpu fullload --cpu-percent " + errorConfigModel.getErrorParams().get(0)
                    + " --container-ids " + containId.substring(9,21)
                    + " --names " + errorConfigModel.getPodName()
                    + " --namespace " + podModel.getNamespace()
                    + "  --kubeconfig /root/.kube/config";

        }else if(StringUtils.equalsIgnoreCase(errorInjectModel.getErrorKey(),"micro_svc_press")){

            MicroSvcPressParamModel microSvcPressParamModel = new MicroSvcPressParamModel();
            microSvcPressParamModel.setAccountNum(Integer.parseInt(errorConfigModel.getErrorParams().get(0)));
            microSvcPressParamModel.setWaitTime(Integer.parseInt(errorConfigModel.getErrorParams().get(1)));
            microSvcPressParamModel.setLoopTime(Integer.parseInt(errorConfigModel.getErrorParams().get(2)));

            order = JSON.toJSONString(microSvcPressParamModel);

        }else if(StringUtils.equalsIgnoreCase(errorInjectModel.getErrorKey(),"gpu_error")){

            order = errorConfigModel.getErrorParams().get(0);

        }

        errorInjectModel.setErrorOrder(order);
        knowledgeMapper.saveErrorInjectModel(errorInjectModel);
    }

    /**
     *
     * @param url
     * @param param
     * @return
     */
    private String doHttpPostReq(String url, StringEntity param){

        try {

            CloseableHttpClient httpclient = HttpClients.createDefault();

            HttpPost httpPost = new HttpPost(url);

            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(5 * 60 * 1000).setConnectionRequestTimeout(5 * 60 * 1000)
                    .setSocketTimeout(5 * 60 * 1000).build();
            httpPost.setConfig(requestConfig);
            httpPost.setEntity(param);

            CloseableHttpResponse response = httpclient.execute(httpPost);
            return EntityUtils.toString(response.getEntity());

        }catch (Throwable e){
            LOGGER.error("addErrorInject doHttpPostReq fail url={}",url,e);
        }

        return null;
    }
}
